﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace PerfAnalyzerLib
{
	public class PerfLog
	{
		/// <summary>
		/// FileInfo of the perflog.
		/// </summary>
		private FileInfo _fileInfo;

		/// <summary>
		/// List of counters.
		/// </summary>
		private List<Counter> _counters = new List<Counter>();

		/// <summary>
		/// Perflogs timestamps.
		/// </summary>
		private double[] _ticks;

		public FileInfo FileInfo { get { return _fileInfo; } }

		/// <summary>
		/// Byte interval at which we send update during parsing
		/// </summary>
		private const long _updateFrequency = 3000000;

		#region Events stuff
        public delegate void ParsingStatusEventHandler(object sender, PerflobParsingStatusEventArgs e);
		public event ParsingStatusEventHandler ParsingStatusUpdate;

        private void OnParsingStatusUpdate(PerflobParsingStatusEventArgs e)
		{
			ParsingStatusEventHandler handler = ParsingStatusUpdate;
			if (handler != null) handler(this, e);
		}
		#endregion

		/// <summary>
		/// List of counters property.
		/// </summary>
		public List<Counter> Counters
		{
			get { return _counters; }
		}

		public double[] Ticks
		{
			get { return _ticks; }
		}

		/// <summary>
		/// Full name of the file.
		/// </summary>
		public string Filename { get { return _fileInfo.FullName; } }

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="filename">Perflogs filename.</param>
		public PerfLog(string filename)
		{
			_fileInfo = new FileInfo(filename);
		}

		/// <summary>
		/// Parse Perflog
		/// </summary>
		public void Parse()
		{
			using (var fp = new FileParser(_fileInfo.FullName))
			{
				long totalBytesRead = 0;
				long bytesReadSinceLastUpdate = 0;

				//--------------------------------
				// Read header file (column names)
				//--------------------------------

				string line = fp.GetLine();

				// Check for invalid file
				if (null == line)
				{
					throw new Exception("Empty file: " + _fileInfo.FullName);
				}

				totalBytesRead += line.Length;
				bytesReadSinceLastUpdate += line.Length;

				// Get counter names
				string[] counterNames = SplitRows(line);

				// Need at least 2 columns
				if (counterNames.Length < 2)
				{
					throw new Exception("Need at least 2 columns in perf log.");
				}

				// Create a list (counters) containing lists (values)
				var arrays = new List<double>[counterNames.Length];
				for (int i = 0; i < counterNames.Length; i++ )
				{
					arrays[i] = new List<double>();
				}

				var timestamps = new List<double>();

				//------------------ 
				// Read rest of file
				//------------------

				int lineCount = 1;
				while ((line = fp.GetLine()) != null)
				{
					totalBytesRead += line.Length;
					bytesReadSinceLastUpdate += line.Length;
					lineCount++;
					string[] valuesToParse = SplitRows(line);

					// Make sure we have the right number of columns
					if (valuesToParse.Length != counterNames.Length)
					{
						throw new Exception("Number of column in line " + lineCount + " does not match the number of counters in file " + _fileInfo.FullName);
					}

					// Parse timestamp
					DateTime timestamp;
					if (!DateTime.TryParse(valuesToParse[0], out timestamp))
					{
						throw new Exception("Could not parse the timestamp column with value \"" + valuesToParse[0] + "\" in the first column at line " + lineCount + " in file " + _fileInfo.FullName + ". Verrify that the first column has a valid timestamp.");
					}

					timestamps.Add(timestamp.ToOADate());

					// Parse each values
					for (int i = 1; i < arrays.Length; i++)
					{
						double parsedValue;

						if (!double.TryParse(valuesToParse[i], out parsedValue))
						{
							if (string.IsNullOrWhiteSpace(valuesToParse[i]))
							{
								parsedValue = 0;
							}
							else
							{
								throw new Exception("Could not parse \"" + valuesToParse[i] + "\" in column " + (i + 1) + ", line " + lineCount + " in file " + _fileInfo.FullName);
							}
						}

						arrays[i].Add(parsedValue);
					}

					// Send event for update
					if (bytesReadSinceLastUpdate > _updateFrequency)
					{
						bytesReadSinceLastUpdate = 0;
						float parsingPercentage = (float)totalBytesRead / (float)_fileInfo.Length * 100;
						OnParsingStatusUpdate(new PerflobParsingStatusEventArgs(parsingPercentage));
					}
				}

				// Transform lists into arrays
				for (int i = 1; i < arrays.Length; i++)
				{
					var counter = new Counter(counterNames[i]) {Values = arrays[i].ToArray(), Perflog = this};
					_counters.Add(counter);
				}

				_ticks = timestamps.ToArray();
			}
		}

		/// <summary>
		/// Export counter info and statistics in a string format.
		/// </summary>
		/// <returns>StringBuilder.</returns>
		public string Export()
		{
			var sb = new StringBuilder();
			int counters = 0;

			// perflog, counter, avg, min, max, 25th, 50th, 75th, 90th, 95th, 99th
			foreach (var counter in _counters)
			{
				counters++;

				sb.AppendFormat("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11}", this._fileInfo.Name, counter.Info.Machine, counter.Info.GetExtendedCounterName(), counter.Stats.Average, counter.Stats.Min, counter.Stats.Max, counter.Stats.Percentile25th, counter.Stats.Percentile50th, counter.Stats.Percentile75th, counter.Stats.Percentile90th, counter.Stats.Percentile95th, counter.Stats.Percentile99th);
				sb.AppendLine();
			}

			return sb.ToString();
		}

		/// <summary>
		/// Splits rows with either , or ",".
		/// </summary>
		/// <param name="line"></param>
		/// <returns></returns>
		private string[] SplitRows(string line)
		{
			string[] splits;

			if (line.StartsWith("\""))
			{
				line = line.Trim(new[] {'\"'});
				splits = line.Split(new string[] {"\",\""}, StringSplitOptions.None);
			}
			else
			{
				splits = line.Split(new[] {","}, StringSplitOptions.None);
			}

			return splits;
		}
	}

    public class PerflobParsingStatusEventArgs : EventArgs
	{
		public float ParsingPercentage;

		public PerflobParsingStatusEventArgs (float parsingPercentage)
		{
			ParsingPercentage = parsingPercentage;
		}
	}
}
